package com.solution.framework.dao.impl;

import com.solution.framework.dao.BaseDao;
import com.solution.framework.model.BaseBean;
import com.solution.framework.util.Pagination;
import org.hibernate.*;
import org.hibernate.criterion.*;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Table;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.*;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * Busline: sunguangzhu
 * Date: 13-6-18
 * Time: 下午1:20
 * To change this template use File | Settings | File Templates.
 */
@Repository
public class BaseDaoImpl<T extends BaseBean> extends HibernateDaoSupport implements BaseDao<T> {
    private Class<?> clazz;
    protected static final int batchSize = 50;

    @Qualifier(value = "sessionFactory")
    @Autowired
    public void setSessionFactoryOverride(SessionFactory sessionFactory) {
        super.setSessionFactory(sessionFactory);
    }

    public BaseDaoImpl() {
        Type genericSuperclass = getClass().getGenericSuperclass();
        if (genericSuperclass instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
            Type[] types = parameterizedType.getActualTypeArguments();
            clazz = (Class<T>) types[0];
        }
    }

    @Override
    public void save(T entity) {
        Date date = new Date();
        entity.setCreateTime(date);
        entity.setUpdateTime(date);
        this.getHibernateTemplate().save(entity);
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void update(T entity) {
        entity.setUpdateTime(new Date());
        this.getHibernateTemplate().update(entity);
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void delete(T entity) {
        this.getHibernateTemplate().delete(entity);
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public void delete(Serializable id) {
        T entity = this.get(id);
        this.delete(entity);  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public int delete(String ids) {
        final StringBuilder sql = new StringBuilder();
        Table annotation = (Table) clazz.getAnnotation(Table.class);
        String tableName;
        if (annotation != null) {
            tableName = annotation.name();
        } else {
            tableName = clazz.getSimpleName();
        }
        sql.append(" delete from ").append(tableName).append(" where id in ( ").append(ids).append(" )");
        return this.getHibernateTemplate().execute(new HibernateCallback<Integer>() {
            @Override
            public Integer doInHibernate(Session session) throws HibernateException, SQLException {
                SQLQuery sqlQuery = session.createSQLQuery(sql.toString());
                return sqlQuery.executeUpdate();
            }
        });
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public T get(Serializable id) {
        if (id == null) {
            return null;
        }
        return (T) this.getHibernateTemplate().get(this.clazz, id);  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public List<T> listByHql() {
        return this.listByHql(null, null);  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public List<T> listByHql(String hql, Object... params) {
        return this.pageList(hql, null, params);  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public List<T> pageList(Pagination pagination) {
        return this.pageList(null, pagination);  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public List<T> pageList(String hql, final Pagination pagination, final Object... params) {
        if (hql == null) {
            hql = "from " + clazz.getSimpleName();
        }
        final String finalHql = hql;
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) throws HibernateException, SQLException {
                Query query = session.createQuery(finalHql);
                if (params != null) {
                    query = createQueryParams(query, params);
                }
                if (pagination != null) {
                    query.setFirstResult(pagination.getFirstTotal());
                    query.setMaxResults(pagination.getPageSize());
                }
                return query.list();
            }
        });
        //To change body of implemented methods use File | Settings | File Templates.
    }


    @Override
    public int getTotal(String hql, final Object... params) {
        String defaultHql = " select count(*) from " + clazz.getSimpleName();
        if (hql != null) {
            defaultHql = defaultHql + hql;
        }
        final String finalHql = defaultHql;
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) throws HibernateException, SQLException {
                Query query = session.createQuery(finalHql);
                if (params != null) {
                    query = createQueryParams(query, params);
                }
                return Integer.parseInt(query.uniqueResult().toString());
            }
        });
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Integer deleteAll() {
        StringBuilder hql = new StringBuilder()
                .append("delete from ").append(clazz.getSimpleName())
                .append(" obj where 1 = 1 ");

        // 分页
        final String hqlString = hql.toString();
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) {
                Query query = session.createQuery(hqlString);
                return query.executeUpdate();
            }
        });
        //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Collection<T> batchUpdate(Collection<T> entitys) {
        return null;
    }

    @Override
    public Collection<T> batchSave(final Collection<T> entitys) {
        return this.getHibernateTemplate().execute(new HibernateCallback<Collection<T>>() {
            @Override
            public Collection<T> doInHibernate(Session session) throws HibernateException, SQLException {
                T entity = null;
                Iterator<T> iter = entitys.iterator();
                int i = 0;
                while (iter.hasNext()) {
                    entity = iter.next();
                    session.save(entity);
                    i++;
                    if (i % batchSize == 0) {
                        session.flush();
                        session.clear();
                    }
                }
                session.flush();
                session.clear();
                return entitys;
            }
        });
    }

    @Override
    public List<Map<String, Object>> findListMapByHql(String hql) {
        return null;
    }

    @Override
    public Map<String, Object> findUniqueMapBySql(final String sql, final Object... params) {
        return (Map<String, Object>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Map<String, Object> doInHibernate(Session session) {
                SQLQuery sqlQuery = (SQLQuery) session.createSQLQuery(sql).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
                if (params != null) {
                    for (int i = 0; i < params.length; i++) {
                        Object param = params[i];
                        sqlQuery.setParameter(i, param);
                    }
                }
                Map<String, Object> map = (Map<String, Object>) sqlQuery.uniqueResult();
                return map;
            }
        });
    }

    @Override
    public List<Map<String, Object>> findListMapBySql(String sql) {
        return null;  //To change body of implemented methods use File | Settings | File Templates.
    }

    @Override
    public Map<Long, Integer> getLazyObjectsCountMap(final List<Long> entityIds, String parentClassName, final String ChildClassName) {
        parentClassName = parentClassName.substring(0, 1).toLowerCase() + parentClassName.substring(1);
        final String finalParentClassName = parentClassName;
        return (Map<Long, Integer>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Map<Long, Integer> doInHibernate(Session session) {
                Map<Long, Integer> map = new HashMap<Long, Integer>();
                if (entityIds.size() > 0) {
                    StringBuilder stringBuilder = new StringBuilder("");
                    stringBuilder.append(" select ").append(finalParentClassName).append(".id as id,count(c.id) as count from ").append(ChildClassName).append(" c ")
                            .append(" where ").append(finalParentClassName).append(".id in(:parentIds) ").append(" group by ")
                            .append(finalParentClassName).append(".id ");
                    final String hql = stringBuilder.toString();
                    Query query = session.createQuery(hql);
                    query.setParameterList("parentIds", entityIds);
                    List<Object[]> list = query.list();
                    for (Object[] objects : list) {
                        map.put(Long.valueOf(objects[0].toString()), Integer.valueOf(objects[1].toString()));
                    }
                }
                return map;
            }
        });
    }

    private Query createQueryParams(Query query, Object... params) {
        for (int i = 0; params != null && i < params.length; i++) {
            query.setParameter(i, params[i]);
        }
        return query;
    }

    @Override
    public List<T> listByCriteria(Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).list();
            }
        });
        //To change body of implemented methods use File | Settings | File Templates.
    }
    @Override
    public List<T> listByCriteria(Order order, Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).list();
            }
        });
    }

    @Override
    public List<T> listByCriteria(final Pagination pagination, Order order, Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).setFirstResult(pagination.getFirstTotal()).setMaxResults(pagination.getPageSize()).list();
            }
        });
    }


    @Override
    public List<T> listByCriteria(final Pagination pagination, Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).setFirstResult(pagination.getFirstTotal()).setMaxResults(pagination.getPageSize()).list();
            }
        });
    }

    @Override
    public int getTotalByCriteria( Order order, Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) {
                return Integer.parseInt(criteriaParams.setProjection(Projections.rowCount()).getExecutableCriteria(session).uniqueResult().toString());
            }
        });
    }

    @Override
    public int getTotalByCriteria( Criterion... criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) {
                return Integer.parseInt(criteriaParams.setProjection(Projections.rowCount()).getExecutableCriteria(session).uniqueResult().toString());
            }
        });
    }

    @Override
    public List<T> listByCriteria(List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).list();
            }
        });
    }

    @Override
    public List<T> listByCriteria(Order order, List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).list();
            }
        });
    }

    @Override
    public List<T> listByCriteria(final Pagination pagination, Order order, List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback() {
            public List<T> doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).setFirstResult(pagination.getFirstTotal()).setMaxResults(pagination.getPageSize()).list();
            }
        });
    }

    @Override
    public List<T> listByCriteria(final Pagination pagination, List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (List<T>) this.getHibernateTemplate().execute(new HibernateCallback<List>() {
            public List doInHibernate(Session session) {
                return criteriaParams.getExecutableCriteria(session).setFirstResult(pagination.getFirstTotal()).setMaxResults(pagination.getPageSize()).list();
            }
        });
    }

    @Override
    public int getTotalByCriteria(Order order, List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(order,criterions);
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) {
                return Integer.parseInt(criteriaParams.setProjection(Projections.rowCount()).getExecutableCriteria(session).uniqueResult().toString());
            }
        });
    }

    @Override
    public int getTotalByCriteria(List<Criterion> criterions) {
        final DetachedCriteria criteriaParams = this.createCriteriaParams(criterions);
        return (Integer) this.getHibernateTemplate().execute(new HibernateCallback() {
            public Integer doInHibernate(Session session) {
                return Integer.parseInt(criteriaParams.setProjection(Projections.rowCount()).getExecutableCriteria(session).uniqueResult().toString());
            }
        });
    }

    @Override
    public T findUniqueObjectByHql(final String hql, final Object...params) {
        return (T) this.getHibernateTemplate().execute(new HibernateCallback() {
            public T doInHibernate(Session session) throws HibernateException, SQLException {
                Query query = session.createQuery(hql);
                if (params != null) {
                    query = createQueryParams(query, params);
                }
                return (T)query.uniqueResult();
            }
        });
    }

    private DetachedCriteria createCriteriaParams(Order order,Criterion... criterions) {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(clazz);
        for (int i = 0; criterions != null && i < criterions.length; i++) {
            detachedCriteria.add(criterions[i]);
        }
        detachedCriteria.addOrder(order);
        return detachedCriteria;
    }

    private DetachedCriteria createCriteriaParams(Criterion... criterions) {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(clazz);
        for (int i = 0; criterions != null && i < criterions.length; i++) {
            detachedCriteria.add(criterions[i]);
        }
        return detachedCriteria;
    }

    private DetachedCriteria createCriteriaParams(Order order, List<Criterion> criterions) {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(clazz);
        for (Criterion criterion : criterions) {
            detachedCriteria.add(criterion);
        }
        detachedCriteria.addOrder(order);
        return detachedCriteria;
    }

    private DetachedCriteria createCriteriaParams(List<Criterion> criterions) {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(clazz);
        for (Criterion criterion : criterions) {
            detachedCriteria.add(criterion);
        }
        return detachedCriteria;
    }
}
